package loan.calc.calculator.impl;

import loan.calc.calculator.common.DateSelector;
import loan.calc.calculator.common.InterestCalculator;
import loan.calc.calculator.common.InterestRateCalculator;
import loan.calc.calculator.instalment.InstalmentCalculator;
import loan.calc.calculator.instalment.InstalmentCalculatorFactory;
import loan.calc.calculator.instalment.InstalmentCalculatorParamDTO;
import loan.calc.constant.dict.DirectionTypeEnum;
import loan.calc.constant.dict.InstalmentResetFlagEnum;
import loan.calc.constant.dict.RepayMethodDictEnum;
import loan.calc.constant.dict.RepayScheduleTypeEnum;
import loan.calc.constant.dict.rate.RateTypeEnum;
import loan.calc.dto.LoanDTO;
import loan.calc.dto.LoanRateSegmentDTO;
import loan.calc.dto.LoanRepaySegmentDTO;
import loan.calc.dto.LoanRepaymentScheduleDTO;
import loan.calc.util.BigDecimalUtils;
import loan.calc.util.CloneUtils;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;

/**
 * @author v_ECD963
 */
@Component("loanRepaymentScheduleGenerator")
@RequiredArgsConstructor
public class LoanRepaymentScheduleGenerator extends AbstractCalculator {

    private static final Integer SORT_CODE = 500;

    private final InstalmentCalculatorFactory instalmentCalculatorFactory;

    private final LoanRepaymentSegmentNextRepayDateCalculator loanRepaymentSegmentNextRepayDateCalculator;

    @Override
    public Integer calculatorSort() {
        return SORT_CODE;
    }

    @Override
    public String calculatorName() {
        return "还款计划生成器";
    }

    @Override
    public void calculate(LoanDTO loan) {
        List<LoanRepaymentScheduleDTO> repaymentScheduleList = createRepaymentSchedule(CloneUtils.deepClone(loan));
        loan.setRepayScheduleList(repaymentScheduleList);
    }

    private List<LoanRepaymentScheduleDTO>  createRepaymentSchedule(LoanDTO loan) {
        // 当前期数，该期数是所有分区段还款方式组合起来总的顺序期数，
        // 因为还款计划是一个整体，所以虽然分区段还款方式是不同的，但是从还款计划的角度是期数是连续的
        int loanCurrentPeriod = 1;

        // 借据当前业务日期恰好为应还款日的一期还款计划的未还款金额
        BigDecimal currentPrincipalAmt = BigDecimal.ZERO;

        // 借据本金余额
        BigDecimal loanNormalBalanceAmt = loan.getNormalPrincipalBalance().subtract(currentPrincipalAmt);

        List<LoanRateSegmentDTO> loanRateSegmentList = loan.getLoanRateSegmentList();

        List<LoanRepaymentScheduleDTO> scheduleList = new ArrayList<>();

        // 当有剩余本金时，继续计算
        while (loanNormalBalanceAmt.compareTo(BigDecimal.ZERO) > 0) {
            // 下次还款日
            LocalDate nextRepaymentDateTmp = DateSelector.obtainNextRepaymentDate(loan);
            // 上次还款日
            LocalDate lastRepaymentDateTmp = DateSelector.obtainLastRepaymentDate(loan);

            // 还款计划中的期供计算时通过期供本金+期供利息得来
            // 当前执行的还款方式信息（组合还款方式时，各个阶段可能还款方式不同，所以每次都要重新算）
            LoanRepaySegmentDTO currentRepaymentSegment = loan.getLoanRepaySegmentList().stream()
                    .filter(segment -> segment.getNextRepaymentDate().isEqual(nextRepaymentDateTmp))
                    .findFirst().orElseThrow(() -> new IllegalArgumentException("未找到对应的还款区段信息"));

            RepayMethodDictEnum currentRepayMethod = currentRepaymentSegment.getRepayMethod();
            BigDecimal currentSegmentPrincipalBalance = currentRepaymentSegment.getPrincipalBalance();
            Integer repaySegmentCurrentPeriod = currentRepaymentSegment.getCurrentPeriod();
            Integer currentSegmentTotalPeriod = currentRepaymentSegment.getTotalPeriod();

            InstalmentCalculatorParamDTO instalmentCalculatorParam = new InstalmentCalculatorParamDTO();
            instalmentCalculatorParam.setTotalPeriods(currentSegmentTotalPeriod);
            instalmentCalculatorParam.setOutstandingPeriods(currentSegmentTotalPeriod - repaySegmentCurrentPeriod + 1);
            instalmentCalculatorParam.setPeriodRate(
                    InterestRateCalculator.obtainPeriodInterestRate(
                            loanRateSegmentList,
                            lastRepaymentDateTmp,
                            lastRepaymentDateTmp.plus(currentRepaymentSegment.getRepayFrequency(), currentRepaymentSegment.getRepayFrequencyUnit()),
                            currentRepayMethod
                    )
            );
//            instalmentCalculatorParam.setInterestStartDate(lastRepaymentDateTmp);
//            instalmentCalculatorParam.setInterestEndDate(nextRepaymentDateTmp);
            instalmentCalculatorParam.setTotalPrincipalAmount(currentRepaymentSegment.getPrincipalAmount());
            instalmentCalculatorParam.setPrincipalBalance(currentSegmentPrincipalBalance);
            instalmentCalculatorParam.setInstalmentAmt(currentRepaymentSegment.getInstalmentAmt());

            // 获取期供计算器
            InstalmentCalculator instalmentCalculator = instalmentCalculatorFactory.loadInstalmentCalculator(currentRepayMethod,instalmentCalculatorParam);

            // 初始化区段期供数据
            if (!(currentRepaymentSegment.getPsRestructureFlag() == InstalmentResetFlagEnum.NO)) {
                BigDecimal instalmentAmount = instalmentCalculator.calcInstalmentAmount(instalmentCalculatorParam);
                currentRepaymentSegment.setInstalmentAmt(instalmentAmount);
                currentRepaymentSegment.setPsRestructureFlag(InstalmentResetFlagEnum.NO);
            }

            // 期供利息计算基础金额(此处计算期供利息与期供计算器中计算利息的基础金额不同，期供计算器中计算利息的基础金额只为本区段的剩余本金余额，且为整个周期的利息
            // 由于还款计划是针对整个借据的，所以在还款计划中，计算利息的基础金额应为整个借据的剩余本金余额，计息周期也应该是实际的周期
            // 虽然期供计算器中的期供利息计算对于整个借据来说是不正确的，但是在本区段内是正确的，由于还款计划中对期供利息重新做了计算，所以整体上是正确的)
            BigDecimal baseAmountForInterestCalculate;
            switch (currentRepayMethod.getBaseAmountTypeForInstalmentInterest()) {
                case TOTAL_AMOUNT_SUM:
                    baseAmountForInterestCalculate = loan.getApplyAmount();
                    break;
                case NORMAL_BALANCE:
                    baseAmountForInterestCalculate = loan.getNormalPrincipalBalance();
                    break;
                default:
                    throw new IllegalStateException("unsupported base amount type for instalment interest: " + currentRepayMethod.getBaseAmountTypeForInstalmentInterest());
            }
            // 期供利息计算
            BigDecimal instalmentInterestAmt = InterestCalculator.calculateInterest(
                    baseAmountForInterestCalculate,
                    lastRepaymentDateTmp,
                    nextRepaymentDateTmp,
                    loanRateSegmentList,
                    currentRepayMethod.getPeriodSplitMode(),
                    RateTypeEnum.NORMAL_RATE);
            instalmentInterestAmt = BigDecimalUtils.roundCurrency(instalmentInterestAmt);

            // 期供本金计算
            BigDecimal instalmentPrincipalAmt = instalmentCalculator.calcInstalmentPrincipal(instalmentCalculatorParam);

            //判断是否为最后一期,若为最后一期，则最后一期本金为剩余未还金额
            if (instalmentPrincipalAmt.compareTo(currentSegmentPrincipalBalance) > 0
                    || (currentRepaymentSegment.getSegmentStartDate() != null && !nextRepaymentDateTmp.isBefore(currentRepaymentSegment.getSegmentMaturityDate())
                    || repaySegmentCurrentPeriod >= currentSegmentTotalPeriod)) {
                instalmentPrincipalAmt = currentSegmentPrincipalBalance;
            }
            if (nextRepaymentDateTmp.isEqual(loan.getMaturityDate())) {
                instalmentPrincipalAmt = loanNormalBalanceAmt;
            }
            //贷款本金余额
            loanNormalBalanceAmt = loanNormalBalanceAmt.subtract(instalmentPrincipalAmt);

            // 创建还款计划
            LoanRepaymentScheduleDTO repaySchedule = initRepaySchedule(loan, currentRepaymentSegment);
            repaySchedule.setPayPrincipalAmt(instalmentPrincipalAmt);
            repaySchedule.setPayInterestAmt(instalmentInterestAmt);
            repaySchedule.setPeriodNo(loanCurrentPeriod);
            repaySchedule.setPaymentDate(nextRepaymentDateTmp);
            repaySchedule.setPrincipalBalance(loanNormalBalanceAmt);
            //放入还款计划列表
            scheduleList.add(repaySchedule);

            //区段还款信息切到下一个期次
            BigDecimal nextPeriodPrincipalAmt = currentSegmentPrincipalBalance.subtract(instalmentPrincipalAmt);
            //修改本区段余额
            currentRepaymentSegment.setPrincipalBalance(nextPeriodPrincipalAmt.compareTo(BigDecimal.ZERO) <= 0 ? BigDecimal.ZERO : nextPeriodPrincipalAmt);
            currentRepaymentSegment.setLastRepaymentDate(currentRepaymentSegment.getNextRepaymentDate());
            currentRepaymentSegment.setCurrentPeriod(repaySegmentCurrentPeriod + 1 > currentSegmentTotalPeriod ? currentSegmentTotalPeriod : repaySegmentCurrentPeriod + 1);
            // 重新计算下次还款日
            currentRepaymentSegment.setNextRepaymentDate(loanRepaymentSegmentNextRepayDateCalculator.doCalculate(currentRepaymentSegment));
            loan.setBusinessDate(nextRepaymentDateTmp);
            loan.setNormalPrincipalBalance(loanNormalBalanceAmt);
            //当前期数切到下一个期次
            loanCurrentPeriod += 1;
        }
        return scheduleList;
    }

    private static LoanRepaymentScheduleDTO initRepaySchedule(LoanDTO loan, LoanRepaySegmentDTO currentRepaymentSegment) {
        LoanRepaymentScheduleDTO repaySchedule = new LoanRepaymentScheduleDTO();
        repaySchedule.setLoanId(loan.getLoanId());
        repaySchedule.setRepaySegmentId(currentRepaymentSegment.getSegmentId());
        repaySchedule.setRepayScheduleType(RepayScheduleTypeEnum.PI_R_PLAN);
        repaySchedule.setDirection(DirectionTypeEnum.R);
        return repaySchedule;
    }
}
